import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.SwingConstants;
import javax.swing.SwingWorker;
import javax.swing.Timer;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

/**
 * Proof of Concept.
 * Start, stop, pause, resume and control speed of an algorithm.
 *
 * @author Vianney DUPOY DE GUITARd
 * @version r0
 *
 */
public class WorkerTest extends SwingWorker<Void, Void> implements ActionListener {

    /** Timer shortest delay. */
    private static final int MIN_DELAY = 2;

    /** An ordinary object updated by an ordinary algorithm. */
    private int     i = 0;

    /** The main timer. */
    private Timer   timer = new Timer((int) Math.pow(MIN_DELAY, 9), this);

    /** The lock shared between the thread and the timer (CPU optimization). */
    private Object  timerLock = new Object();

    /** Always true, used to make an infinite loop. */
    private boolean running = true;

    /** Help to determine if the timer is paused or not. */
    private boolean paused;

    /**
     * Random algorithm.
     * @return nothing.
     * @throws Exception in case of algorithm failure.
     */
    @Override
    protected Void doInBackground() throws Exception {
        // Infinite loop.
        while (running) {
            // Do nothing until the timer notifies this thread.
            synchronized (timerLock) {
                timerLock.wait();
            }
            // Random algorithm.
            int oldI = i;
            i++;
            // Notifies view that a value has been updated.
            firePropertyChange("i", oldI, i);
        }
        return null;
    }

    /**
     * Action triggered at each timer 'tick'.
     * @param e : triggered event (not used).
     */
    @Override
    public void actionPerformed(final ActionEvent e) {
        // Notifies the algorithm it can continue.
        synchronized (timerLock) {
            timerLock.notify();
        }
    }

    /** Starts the timer, and thus the algorithm. */
    public void start() {
        timer.start();
    }

    /** Stops the timer and resets the algorithm. */
    public void stop() {
        i = 0;
        paused = false;
        timer.stop();
    }

    /** Pauses the timer and algorithm. */
    public void pause() {
        if (timer.isRunning()) {
            timer.stop();
            paused = true;
        }
    }

    /** Restarts the timer and algorithm. */
    public void restart() {
        timer.start();
        paused = false;
    }

    /** @return true or false whether the algorithm is paused or not. */
    public boolean isPaused() {
        return paused;
    }

    /** @return true or false whether the algorithm and timer are running or not. */
    public boolean isRunning() {
        return timer.isRunning() && running;
    }

    /**
     * Define a new timer delay.
     * @param pDelay : new delay.
     */
    public void setDelay(final int pDelay) {
        timer.setDelay(pDelay);
    }

    /** @return the min delay. */
    public int getMinDelay() {
        return MIN_DELAY;
    }

    /**
     * Entry point.
     * @param args : not used.
     */
    public static void main(final String[] args) {
        final WorkerTest testClass = new WorkerTest();

        // Container initialization.
        JFrame frame = new JFrame("Test");
        JPanel ctrlPanel = new JPanel(new FlowLayout());
        JPanel statPanel = new JPanel(new FlowLayout());

        // Control and label initialization.
        final JSlider slider = new JSlider(SwingConstants.HORIZONTAL, 0, 10, 1);
        final JLabel value = new JLabel();
        final JLabel status = new JLabel("i value : ");
        final JButton start = new JButton("Start");
        final JButton pause = new JButton("Pause");

        // Miscellaneous configuration.
        slider.setPaintLabels(true);
        pause.setEnabled(false);
        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        frame.setLayout(new BorderLayout());
        frame.setSize(450, 100);

        // Adding listeners to controls.
        addListeners(testClass, slider, start, pause, value);

        // Filling panels.
        statPanel.add(status);
        statPanel.add(value);
        ctrlPanel.add(start);
        ctrlPanel.add(pause);
        ctrlPanel.add(slider);

        // Filling frame.
        frame.add(ctrlPanel, BorderLayout.NORTH);
        frame.add(statPanel, BorderLayout.CENTER);

        // Showing window and starting worker thread.
        testClass.execute();
        frame.setVisible(true);
    }

    /**
     * Add various listeners to controls.
     * @param testClass : worker thread.
     * @param slider : slider control.
     * @param start : start button.
     * @param pause : pause button.
     * @param value : label showing the algorithm value.
     */
    public static void addListeners(final WorkerTest testClass, final JSlider slider, final JButton start,
            final JButton pause, final JLabel value) {

        // Pause button will start & stop worker thread.
        start.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(final ActionEvent e) {
                if (testClass.isRunning() || testClass.isPaused()) {
                    testClass.stop();
                    pause.setEnabled(false);
                    start.setText("Start");
                } else {
                    testClass.start();
                    pause.setEnabled(true);
                    pause.setText("Pause");
                    start.setText("Stop");
                }
            }
        });

        // Pause button will pause & resume worker thread.
        pause.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(final ActionEvent e) {
                if (testClass.isPaused()) {
                    testClass.restart();
                    pause.setText("Pause");
                } else {
                    testClass.pause();
                    pause.setText("Resume");
                }
            }
        });

        // Slider will update timer delay.
        slider.addChangeListener(new ChangeListener() {
            @Override
            public void stateChanged(final ChangeEvent e) {
                if (!slider.getValueIsAdjusting()) {
                    int rate = 10 - slider.getValue();
                    testClass.setDelay((int) (Math.pow(testClass.getMinDelay(), rate)));
                }
            }
        });

        // Listen to worker thread.
        testClass.addPropertyChangeListener(new PropertyChangeListener() {
            @Override
            public void propertyChange(final PropertyChangeEvent evt) {
                // Value is updated here.
                if (evt.getPropertyName().equals("i")) {
                    value.setText(evt.getNewValue().toString());
                }
            }
        });
    }
}
